import("cpp");
MPI = new cpp.Plugin("MPI");
with( MPI )
{
        Include("<mpi.h>");
        Include("<stdio.h>");
        Include("<stddef.h>");
        Include("<stdlib.h>");
        Include("../plugin/CArray.h");
        Include("../plugin/CMatrix.h");
        //Include("<linux/crc32.h>");

        Extra = <% 

        #define BUFFER_SIZE 8128
        #define DEBUG 4

        typedef struct fragment_s
        {
            char data[BUFFER_SIZE];
            int position;
            struct fragment_s *next;
        } fragment;

        typedef struct cInfo_s
        {
            int cType;
            int rows;
            int cols;
            int nextLen;
        } cInfo;

        enum 
        {
            jsMPI_MAX = 1,
            jsMPI_MIN,
            jsMPI_SUM,
            jsMPI_PROD,
            jsMPI_LAND,
            jsMPI_BAND,
            jsMPI_LOR,
            jsMPI_BOR,
            jsMPI_LXOR,
            jsMPI_BXOR,
            jsMPI_UPPERLIMIT
        };

        MPI_Op op_table[] = {
            MPI_MAX, MPI_MAX, MPI_MIN, MPI_SUM, MPI_PROD, MPI_LAND, 
            MPI_BAND, MPI_LOR, MPI_BOR, MPI_LXOR, MPI_BXOR 
        };

        bool op_dblCompat[] = { 
            true, true, true, true, true, false, 
            false, false, false, false, false
        };        
        
        bool ExecuteString( Handle<String> source, Handle<Value> name, Handle<Value>& );    
            
        const int jsMPI_Integer = 1;
        const int jsMPI_String = 2;
        const int jsMPI_Number = 3;
        const int jsMPI_Boolean = 4;
        const int jsMPI_Array = 5;
        const int jsMPI_Object = 6;
        const int jsMPI_Function = 7;
        const int jsMPI_Reduce_SUM = 8;
        const int jsMPI_Reduce_PROD = 9;
        const int jsMPI_Fragment = 10;
        const int jsMPI_BcastHdr = 11;
        const int jsMPI_CArray_Int = 42;
        const int jsMPI_CArray_Dbl = 47;
        const int jsMPI_CArray_Str = 49;
        const int jsMPI_CMatrix_Int = 43;
        const int jsMPI_CMatrix_Dbl = 48;


        int jsMPI_Integer_Size;
        int jsMPI_Double_Size;
        int jsMPI_Overhead;
        
        MPI_Status rstatus;     
        int last_rcv_source=0, last_rcv_tag=0;
        int rank;
        int size;

        extern Persistent<Context> context;
        extern Persistent<Object> ClassDict;
        Persistent<Function> Handle_CArray_Int;
        Persistent<Function> Handle_CArray_Dbl;
        Persistent<Function> Handle_CArray_Str;
        Persistent<Function> Handle_CMatrix_Int;
        Persistent<Function> Handle_CMatrix_Dbl;

        Handle<Function> jsMPI_Op_Handle;
        MPI_Op jsMPI_Op_Custom;

        void jsMPI_Op_Link( void *inv, void *outv, int *count, MPI_Datatype *dtype )
        {
            HandleScope handle_scope;
            Handle<Object> InObj;
            Handle<Object> OutObj;

            // Link input and output to CArray
            Handle<Value> ArgvIn[2] = { Int32::New( *count ), External::New( inv ) };
            Handle<Value> ArgvOut[2] = { Int32::New( *count ), External::New( outv ) };

            if ( *dtype == MPI_INT )
            {
                InObj = Handle_CArray_Int->NewInstance( 2, ArgvIn );
                OutObj = Handle_CArray_Int->NewInstance( 2, ArgvOut );
            }
            else if ( *dtype == MPI_DOUBLE )
            {
                InObj = Handle_CArray_Dbl->NewInstance( 2, ArgvIn );
                OutObj = Handle_CArray_Dbl->NewInstance( 2, ArgvOut );
            }
            else if ( *dtype == MPI_BYTE )
            {
                InObj = Handle_CArray_Str->NewInstance( 2, ArgvIn );
                OutObj = Handle_CArray_Str->NewInstance( 2, ArgvOut );
            }

            Handle<Value> Argv[2] = { InObj, OutObj };
            Handle<Object> Parent;

            Handle<Value> Result = jsMPI_Op_Handle->Call( context->Global(), 2, Argv );
            return;
        }

        void ExtendBuffer( fragment **buffer, bool inclLength )
        {
            int lengthFiller;

            // Mark fragment
            MPI_Pack( (void *)&jsMPI_Fragment, 1, MPI_BYTE, (*buffer)->data, BUFFER_SIZE, &(*buffer)->position, MPI_COMM_WORLD );

            // Push new fragment onto list;
            fragment *frag = new fragment;
            frag->next = *buffer;
            frag->position = 0;
            *buffer = frag;

            // Reserve space for buffer length
            if ( inclLength )
                MPI_Pack( &lengthFiller, 1, MPI_INT, (*buffer)->data, BUFFER_SIZE, &(*buffer)->position, MPI_COMM_WORLD );
        }

        int SendCType( Handle<Value> sval, bool isBcast, int sdst, int stag = -1 )
        {
            // Packing vars
            char hdrbuf[16];
            int hdrpos = 0;
            int hdrsize = 16*sizeof(char);
            int fillerLength = 0;

            // MPI info
            void *sdata;
            int sdatalen;
            MPI_Datatype sdatatype;

            // Object type
            unsigned char cType = 0;

            // Check for CArray/CMatrix
            if ( sval->IsObject() )
            {
                Handle<Object> Obj = sval->ToObject();
                if ( Obj->InternalFieldCount() == 2 )
                {
                    // Filter out certain types
                    cType = Obj->GetInternalField( 0 )->Int32Value();
                    switch ( cType )
                    {
                        case jsMPI_CArray_Int:
                        case jsMPI_CArray_Dbl:
                        case jsMPI_CArray_Str:
                            // Prepare header
                            carray_gen *array = (carray_gen *)Local<External>::Cast( Obj->GetInternalField( 1 ) )->Value();
                            if (isBcast)
                                MPI_Pack( &fillerLength, 1, MPI_INT, hdrbuf, hdrsize, &hdrpos, MPI_COMM_WORLD );
                            MPI_Pack( &cType, 1, MPI_BYTE, hdrbuf, hdrsize, &hdrpos, MPI_COMM_WORLD );
                            MPI_Pack( &array->size, 1, MPI_INT, hdrbuf, hdrsize, &hdrpos, MPI_COMM_WORLD );

                            // Prepare send
                            sdata = array->array;
                            sdatalen = array->size;
                            break;

                        case jsMPI_CMatrix_Int:
                        case jsMPI_CMatrix_Dbl:
                            cmatrix_gen *matrix = (cmatrix_gen *)Local<External>::Cast( Obj->GetInternalField( 1 ) )->Value();
                            if (isBcast)
                                MPI_Pack( &fillerLength, 1, MPI_INT, hdrbuf, hdrsize, &hdrpos, MPI_COMM_WORLD );
                            MPI_Pack( &cType, 1, MPI_BYTE, hdrbuf, hdrsize, &hdrpos, MPI_COMM_WORLD );
                            MPI_Pack( &matrix->rows, 1, MPI_INT, hdrbuf, hdrsize, &hdrpos, MPI_COMM_WORLD );
                            MPI_Pack( &matrix->cols, 1, MPI_INT, hdrbuf, hdrsize, &hdrpos, MPI_COMM_WORLD );

                            // Prepare send
                            sdata = matrix->data;
                            sdatalen = matrix->rows * matrix->cols;
                            break;
                    }
                }
            }

            // Check datatype
            if ( cType == jsMPI_CArray_Int || cType == jsMPI_CMatrix_Int )
                sdatatype = MPI_INT;
            else if ( cType == jsMPI_CArray_Dbl || cType == jsMPI_CMatrix_Dbl )
                sdatatype = MPI_DOUBLE;
            else if ( cType == jsMPI_CArray_Str )
                sdatatype = MPI_BYTE;
            else
                return 0;

            // Send header and data
            if ( isBcast )
            {
                // Broadcast header
                int hdr[2] = { 1, hdrpos };
                MPI_Bcast( hdr, 2, MPI_INT, sdst, MPI_COMM_WORLD );

                // Broadcast message
                MPI_Bcast( hdrbuf, hdrpos, MPI_PACKED, sdst, MPI_COMM_WORLD );
                MPI_Bcast( sdata, sdatalen, sdatatype, sdst, MPI_COMM_WORLD );
            }
            else
            {
                MPI_Bsend( hdrbuf, hdrpos, MPI_PACKED, sdst, stag, MPI_COMM_WORLD );
                MPI_Send( sdata, sdatalen, sdatatype, sdst, stag, MPI_COMM_WORLD );
            }

            return cType;
        }

        Handle<Value> ReceiveCType( cInfo *info, bool isBcast, int src, int tag = -1 )
        {
            MPI_Status status;

            // Create CArray object
            Handle<Value> ArgV[2];
            Handle<Object> ValObj;
            switch( info->cType )
            {
                case jsMPI_CArray_Int:
                    ArgV[0] = Int32::New( info->cols );
                    ValObj = Handle_CArray_Int->NewInstance( 1, ArgV );
                    break;

                case jsMPI_CArray_Dbl:
                    ArgV[0] = Int32::New( info->cols );
                    ValObj = Handle_CArray_Dbl->NewInstance( 1, ArgV );
                    break;

                case jsMPI_CArray_Str:
                    ArgV[0] = Int32::New( info->cols );
                    ValObj = Handle_CArray_Str->NewInstance( 1, ArgV );
                    break;

                case jsMPI_CMatrix_Int:
                    ArgV[0] = Int32::New( info->rows );
                    ArgV[1] = Int32::New( info->cols );
                    ValObj = Handle_CMatrix_Int->NewInstance( 2, ArgV );
                    break;

                case jsMPI_CMatrix_Dbl:
                    ArgV[0] = Int32::New( info->rows );
                    ArgV[1] = Int32::New( info->cols );
                    ValObj = Handle_CMatrix_Dbl->NewInstance( 2, ArgV );
                    break;

                default: 
                    return Undefined();
            }

            // Prepare MPI_Recv / MPI_Broadcast
            void *cData = 0;
            if ( info->cType == jsMPI_CArray_Int || info->cType == jsMPI_CArray_Dbl || info->cType == jsMPI_CArray_Str )
            {
                carray_gen *array = (carray_gen *)Local<External>::Cast( ValObj->GetInternalField( 1 ) )->Value();
                cData = array->array;
            }
            else
            {
                cmatrix_gen *matrix = (cmatrix_gen *)Local<External>::Cast( ValObj->GetInternalField( 1 ) )->Value();
                cData = matrix->data;
            }

            // Infer data type
            MPI_Datatype cDataType;
            if ( info->cType == jsMPI_CArray_Int || info->cType == jsMPI_CMatrix_Int )
                cDataType = MPI_INT;
            else if ( info->cType == jsMPI_CArray_Dbl || info->cType == jsMPI_CMatrix_Dbl )
                cDataType = MPI_DOUBLE;
            else
                cDataType = MPI_BYTE;

            // Receive
            if ( isBcast )
                MPI_Bcast( cData, info->rows*info->cols, cDataType, src, MPI_COMM_WORLD );
            else
                MPI_Recv( cData, info->rows*info->cols, cDataType, src, tag, MPI_COMM_WORLD, &status );

            return ValObj;
        }
        
        //*************************** jsMPI_Pack *******************************//
        void jsMPI_Pack( fragment **buffer, Handle<Value> item, bool inclLength )
        {
            if ( item->IsInt32() )
            {
                // Integer
                if ( (*buffer)->position + jsMPI_Integer_Size + jsMPI_Overhead >= BUFFER_SIZE )
                    ExtendBuffer( buffer, inclLength );

                int value = item->IntegerValue();
                MPI_Pack( (void *)&jsMPI_Integer, 1, MPI_BYTE, (*buffer)->data, BUFFER_SIZE, &(*buffer)->position, MPI_COMM_WORLD );
                MPI_Pack( &value, 1, MPI_INT, (*buffer)->data, BUFFER_SIZE, &(*buffer)->position, MPI_COMM_WORLD );
            }
            else if ( item->IsString() || item->IsFunction())
            {
                // String
                String::Utf8Value Str( item->ToString() );
                int StrLength = Str.length();
                int PackLength;
                MPI_Pack_size( StrLength, MPI_BYTE, MPI_COMM_WORLD, &PackLength );
                int MpiLength = jsMPI_Overhead + jsMPI_Integer_Size*2 + PackLength;
                int est = MpiLength + (*buffer)->position;
                if ( MpiLength < BUFFER_SIZE )
                {
                    if ( (*buffer)->position + MpiLength + jsMPI_Integer_Size >= BUFFER_SIZE )
                       ExtendBuffer( buffer, inclLength );
                }
                else
                {
                    ThrowException( String::New( "String too large" ) );
                    return;
                }

                if ( item->IsString() )
                    MPI_Pack( (void *)&jsMPI_String, 1, MPI_BYTE, (*buffer)->data, BUFFER_SIZE, &(*buffer)->position, MPI_COMM_WORLD );
                else
                    MPI_Pack((void *)&jsMPI_Function, 1, MPI_BYTE, (*buffer)->data, BUFFER_SIZE, &(*buffer)->position, MPI_COMM_WORLD);
                MPI_Pack( &StrLength, 1, MPI_INT, (*buffer)->data, BUFFER_SIZE, &(*buffer)->position, MPI_COMM_WORLD );
                MPI_Pack( *Str, Str.length(), MPI_BYTE, (*buffer)->data, BUFFER_SIZE, &(*buffer)->position, MPI_COMM_WORLD );
            }
            else if ( item->IsNumber() )
            {
                // Double
                if ( (*buffer)->position + jsMPI_Double_Size + jsMPI_Overhead >= BUFFER_SIZE )
                    ExtendBuffer( buffer, inclLength );

                double value = item->NumberValue();
                MPI_Pack( (void *)&jsMPI_Number, 1, MPI_BYTE, (*buffer)->data, BUFFER_SIZE, &(*buffer)->position, MPI_COMM_WORLD );
                MPI_Pack( &value, 1, MPI_DOUBLE, (*buffer)->data, BUFFER_SIZE, &(*buffer)->position, MPI_COMM_WORLD );
            }
            else if ( item->IsBoolean() )
            {
                // Boolean
                if ( (*buffer)->position + jsMPI_Integer_Size + jsMPI_Overhead >= BUFFER_SIZE )
                    ExtendBuffer( buffer, inclLength );

                int value = (int)item->BooleanValue();
                MPI_Pack( (void *)&jsMPI_Boolean, 1, MPI_BYTE, (*buffer)->data, BUFFER_SIZE, &(*buffer)->position, MPI_COMM_WORLD );
                MPI_Pack( &value, 1, MPI_INT, (*buffer)->data, BUFFER_SIZE, &(*buffer)->position, MPI_COMM_WORLD );
            }
            else if ( item->IsObject() || item->IsArray() )
            {
                // Objects
                if ( (*buffer)->position + jsMPI_Integer_Size*2 + jsMPI_Overhead >= BUFFER_SIZE )
                    ExtendBuffer( buffer, inclLength );

                Handle<Object> Obj = item->ToObject();
                Handle<Array> ObjKeys = Obj->GetPropertyNames();
                int KeyCount = ObjKeys->Length();

                if ( item->IsArray() )
                    MPI_Pack( (void *)&jsMPI_Array, 1, MPI_BYTE, (*buffer)->data, BUFFER_SIZE, &(*buffer)->position, MPI_COMM_WORLD );
                else
                    MPI_Pack( (void *)&jsMPI_Object, 1, MPI_BYTE, (*buffer)->data, BUFFER_SIZE, &(*buffer)->position, MPI_COMM_WORLD );
                MPI_Pack( (void *)&KeyCount, 1, MPI_INT, (*buffer)->data, BUFFER_SIZE, &(*buffer)->position, MPI_COMM_WORLD );
                for ( int i=0; i<ObjKeys->Length(); i++ )
                {
                    HandleScope handle_scope;
                    Handle<Value> Key = ObjKeys->Get( Integer::New( i ) );
                    Handle<Value> Val = Obj->Get( Key );
                    jsMPI_Pack( buffer, Key, inclLength );
                    jsMPI_Pack( buffer, Val, inclLength );
                }
            } else
                printf("Unrecognized element\n");
        }
        
        //********************************** jsMPI_Unpack **************************************//
        Handle<Value> jsMPI_Unpack( fragment *&buffer, cInfo *info )
        {
            Handle<Object> Obj;
            Handle<Array> ObjArr;
            Handle<Value> key;
            Handle<Value> value;
            int result; 

            unsigned char jsMPI_Type ;
            result = MPI_Unpack( buffer->data, BUFFER_SIZE, &buffer->position, &jsMPI_Type, 1, MPI_BYTE, MPI_COMM_WORLD );
            if (jsMPI_Type == jsMPI_Fragment)
            {
                // Move to next fragment
                int fillerLength;
                fragment *oldFrag = buffer;
                buffer = buffer->next;
                result = MPI_Unpack( buffer->data, BUFFER_SIZE, &buffer->position, &jsMPI_Type, 1, MPI_BYTE, MPI_COMM_WORLD );

                delete oldFrag;
            }

            // Return type
            info->cType = jsMPI_Type;
            switch ( jsMPI_Type )
            {
                case jsMPI_Integer:
                    int intvalue;
                    MPI_Unpack( buffer->data, BUFFER_SIZE, &buffer->position, &intvalue, 1, MPI_INT, MPI_COMM_WORLD );
                    return Integer::New( intvalue );

                case jsMPI_String:
                case jsMPI_Function:
                    int length;
                    char str[BUFFER_SIZE];
                    MPI_Unpack( buffer->data, BUFFER_SIZE, &buffer->position, &length, 1, MPI_INT, MPI_COMM_WORLD );
                    MPI_Unpack( buffer->data, BUFFER_SIZE, &buffer->position, str, length, MPI_BYTE, MPI_COMM_WORLD );
                    if(jsMPI_Type == jsMPI_Function)
                    {
                        Handle<Value> Result;
                        ExecuteString( String::New( str, length ), String::New( "MPI.Recv Function" ), Result );
                        return Result;
                    }
                    else
                        return String::New( str, length );
        
                case jsMPI_Number:
                    double dblvalue;
                    MPI_Unpack( buffer->data, BUFFER_SIZE, &buffer->position, &dblvalue, 1, MPI_DOUBLE, MPI_COMM_WORLD );
                    return Number::New( dblvalue );
        
                case jsMPI_Boolean:
                    int boolvalue;
                    MPI_Unpack( buffer->data, BUFFER_SIZE, &buffer->position, &boolvalue, 1, MPI_INT, MPI_COMM_WORLD );
                    return Boolean::New( (bool) boolvalue );

                case jsMPI_Object:
                case jsMPI_Array:
                    int KeyCount,i;
                    MPI_Unpack(buffer->data, BUFFER_SIZE, &buffer->position, &KeyCount, 1, MPI_INT, MPI_COMM_WORLD);
                    Obj = Object::New();
                    ObjArr = Array::New();
                    key;
                    value;
                    for(i=0; i<KeyCount; i++)
                    {
                        key=jsMPI_Unpack(buffer, info);
                        value=jsMPI_Unpack(buffer, info);
                        if(jsMPI_Type == jsMPI_Object)
                            Obj->Set(key, value);
                        else
                            ObjArr->Set(key, value);        
                    }

                    if(jsMPI_Type == jsMPI_Object)
                        return Obj;
                    else
                        return ObjArr;
                    break;

                case jsMPI_CArray_Int:
                case jsMPI_CArray_Dbl:
                case jsMPI_CArray_Str:
                    MPI_Unpack( buffer->data, BUFFER_SIZE, &buffer->position, &info->cols, 1, MPI_INT, MPI_COMM_WORLD );
                    info->rows = 1;
                    return Null();

                case jsMPI_CMatrix_Int:
                case jsMPI_CMatrix_Dbl:
                    MPI_Unpack( buffer->data, BUFFER_SIZE, &buffer->position, &info->rows, 1, MPI_INT, MPI_COMM_WORLD );
                    MPI_Unpack( buffer->data, BUFFER_SIZE, &buffer->position, &info->cols, 1, MPI_INT, MPI_COMM_WORLD );
                    return Null();

                default:
                    printf("Unknown type: %d %d %d\n", jsMPI_Type, buffer->position, result);
                    fflush(stdout);
            }
        }
        %>

        //****************************** MPI.Bcast() ****************************//
        Functions.Bcast = <%
            int bcaster = ( args.Length() > 0 ) ? args[0]->Int32Value() : 0;
            Handle<Value> bdata;
            if ( args.Length() > 1 )
                bdata = args[1];
            else
                bdata = False();

            if ( args.Length() == 0 )
                return ThrowException( String::New( "Expected broadcaster" ) );
            
            if ( bcaster == rank )
            {
                // Check for C types
                int cType = SendCType( bdata, true, bcaster );
                if ( cType )
                    return bdata;

                // Pack data into buffer
                int lengthFiller = 0;
                fragment *buffer = new fragment;
                buffer->position = 0;
                buffer->next = 0;
                MPI_Pack( &lengthFiller, 1, MPI_INT, buffer->data, BUFFER_SIZE, &buffer->position, MPI_COMM_WORLD );
                jsMPI_Pack( &buffer, bdata, true );

                // Count fragments
                int numFrags = 1;
                fragment *thisFrag = buffer;
                while (thisFrag->next)
                {
                    numFrags++;
                    thisFrag = thisFrag->next;
                }

                // Broadcast header
                int hdr[2] = { numFrags, buffer->position };
                MPI_Bcast( hdr, 2, MPI_INT, bcaster, MPI_COMM_WORLD );

                // Broadcast fragments
                do
                {
                    int zero = 0;
                    if ( buffer->next )
                        MPI_Pack( &buffer->next->position, 1, MPI_INT, buffer->data, BUFFER_SIZE, &zero, MPI_COMM_WORLD );

                    MPI_Bcast( buffer->data, buffer->position, MPI_PACKED, bcaster, MPI_COMM_WORLD);
                    fragment *oldFrag = buffer;
                    buffer = buffer->next;
                    delete oldFrag;
                } while (buffer);
    
                return bdata;
            }
            else
            {
                // Receive header                
                int hdr[2];
                MPI_Bcast( hdr, 2, MPI_INT, bcaster, MPI_COMM_WORLD );
                int numFragments = hdr[0];
                int nextLen = hdr[1];

                // Receive fragments
                fragment *buffer = 0;
                fragment *newBuffer;
                for ( int i=0; i<numFragments; i++ )
                {
                    newBuffer = new fragment;
                    newBuffer->position = 0;
                    newBuffer->next = buffer;
                    buffer = newBuffer;
                    MPI_Bcast( buffer->data, nextLen, MPI_PACKED, bcaster, MPI_COMM_WORLD );
                    MPI_Unpack( buffer->data, BUFFER_SIZE, &buffer->position, &nextLen, 1, MPI_INT, MPI_COMM_WORLD );
                }

                // Unpack object
                cInfo info;
                Handle<Value> val = jsMPI_Unpack( buffer, &info );
                delete buffer;

                // Check for CArray/CMatrix
                switch ( info.cType )
                {
                    case jsMPI_CArray_Int:
                    case jsMPI_CArray_Dbl:
                    case jsMPI_CMatrix_Int:
                    case jsMPI_CMatrix_Dbl:
                        return ReceiveCType( &info, true, bcaster );
    
                    default: 
                        return val;
                }
            }
        %>

        //****************************** MPI.Init() ****************************//
        Init = <%
            MPI_Init( 0, NULL );
            MPI_Comm_rank( MPI_COMM_WORLD, &rank );
            MPI_Comm_size( MPI_COMM_WORLD, &size );

            // Look up C containers
            Local<Value> Local_CArray_Int_Obj = ClassDict->Get( String::New( "CArray_Integer" ) );
            Local<Value> Local_CArray_Dbl_Obj = ClassDict->Get( String::New( "CArray_Double" ) );
            Local<Value> Local_CArray_Str_Obj = ClassDict->Get( String::New( "CArray_String" ) );
            Local<Value> Local_CMatrix_Int_Obj = ClassDict->Get( String::New( "CMatrix_Integer" ) );
            Local<Value> Local_CMatrix_Dbl_Obj = ClassDict->Get( String::New( "CMatrix_Double" ) );

            // Cast
            Local<Function> Local_CArray_Int;
            Local<Function> Local_CArray_Dbl;
            Local<Function> Local_CArray_Str;
            Local<Function> Local_CMatrix_Int;
            Local<Function> Local_CMatrix_Dbl;
            if ( Local_CArray_Int_Obj != Undefined() )
                Local_CArray_Int = Local<Function>::Cast( Local_CArray_Int_Obj );
            if ( Local_CArray_Dbl_Obj != Undefined() )
                Local_CArray_Dbl = Local<Function>::Cast( Local_CArray_Dbl_Obj );
            if ( Local_CArray_Str_Obj != Undefined() )
                Local_CArray_Str = Local<Function>::Cast( Local_CArray_Str_Obj );
            if ( Local_CMatrix_Int_Obj != Undefined() )
                Local_CMatrix_Int = Local<Function>::Cast( Local_CMatrix_Int_Obj );
            if ( Local_CMatrix_Dbl_Obj != Undefined() )
                Local_CMatrix_Dbl = Local<Function>::Cast( Local_CMatrix_Dbl_Obj );

            // Set
            Handle_CArray_Int = Persistent<Function>::New( Local_CArray_Int );
            Handle_CArray_Dbl = Persistent<Function>::New( Local_CArray_Dbl );
            Handle_CArray_Str = Persistent<Function>::New( Local_CArray_Str );
            Handle_CMatrix_Int = Persistent<Function>::New( Local_CMatrix_Int );
            Handle_CMatrix_Dbl = Persistent<Function>::New( Local_CMatrix_Dbl );

            // Initial jsMPI
            MPI_Pack_size( 1, MPI_DOUBLE, MPI_COMM_WORLD, &jsMPI_Double_Size );
            MPI_Pack_size( 1, MPI_INT, MPI_COMM_WORLD, &jsMPI_Integer_Size );
            jsMPI_Overhead = jsMPI_Integer_Size * 2;

            // Constants for MPI.Reduce()
            obj->Set( String::New( "Max" ), Int32::New( jsMPI_MAX ) );
            obj->Set( String::New( "Min" ), Int32::New( jsMPI_MIN ) );
            obj->Set( String::New( "Sum" ), Int32::New( jsMPI_SUM ) );
            obj->Set( String::New( "Prod" ), Int32::New( jsMPI_PROD ) );
            obj->Set( String::New( "Land" ), Int32::New( jsMPI_LAND ) );
            obj->Set( String::New( "Band" ), Int32::New( jsMPI_BAND ) );
            obj->Set( String::New( "Lor" ), Int32::New( jsMPI_LOR ) );
            obj->Set( String::New( "Bor" ), Int32::New( jsMPI_BOR ) );
            obj->Set( String::New( "Lxor" ), Int32::New( jsMPI_LXOR ) );
            obj->Set( String::New( "Bxor" ), Int32::New( jsMPI_BXOR ) );

            MPI_Op_create( jsMPI_Op_Link, false, &jsMPI_Op_Custom );
        %>
        
        //****************************** MPI.Size() ****************************//
        Functions.Size = <%
            int size;
            MPI_Comm_size( MPI_COMM_WORLD, &size );
            return Int32::New( size );
        %>
        
        //****************************** MPI.Rank() ****************************//
        Functions.Rank = <%
            int rank;
            MPI_Comm_rank( MPI_COMM_WORLD, &rank );
            return Int32::New( rank );
        %>
        
        //****************************** MPI.Send() ****************************//
        Functions.Send = <%
            Handle<Value> sval;
            if (args.Length() > 0 )
                sval = args[0];
            else
                sval = Null();
            int sdst = ( args.Length() > 1 ) ? args[1]->Int32Value() : 0;
            int stag = ( args.Length() > 2 ) ? args[2]->Int32Value() : 0;

            // Check arguments
            if ( args.Length() < 2 )
                return ThrowException( String::New( "Data and recipient required" ) );

            // Check for C types
            int cType = SendCType( sval, false, sdst, stag );
            if ( cType )
                return Undefined();

            // Pack object into buffer
            int position = 0;
            fragment *buffer = new fragment;
            buffer->position = 0;
            buffer->next = 0;
            jsMPI_Pack( &buffer, sval, false );

            // Count fragments
            int numFrags = 1;
            fragment *thisFrag = buffer;
            while (thisFrag->next)
            {
                numFrags++;
                thisFrag = thisFrag->next;
            }

            // Send fragment header if necessary
            if ( numFrags > 1 )
            {
                char hdrbuf[16];
                int hdrpos = 0;
                MPI_Pack( &numFrags, 1, MPI_INT, hdrbuf, 16*sizeof(char), &hdrpos, MPI_COMM_WORLD );
                MPI_Send( hdrbuf, hdrpos, MPI_PACKED, sdst, stag, MPI_COMM_WORLD );
            }

            // Send fragments
            do
            {
                MPI_Send( buffer->data, buffer->position, MPI_PACKED, sdst, stag, MPI_COMM_WORLD);
                fragment *oldFrag = buffer;
                buffer = buffer->next;
                delete oldFrag;
            } while (buffer);

            return Undefined();
        %>
        
        //****************************** MPI.Recv() ****************************//
        Functions.Recv = <%
            int src = ( args.Length() > 0 ) ? args[0]->Int32Value() : MPI_ANY_SOURCE; 
            int rtag = ( args.Length() > 1 ) ? args[0]->Int32Value() : MPI_ANY_TAG;

            // Receive message                
            fragment *buffer = new fragment;
            buffer->position = 0;
            buffer->next = 0;
            MPI_Recv( buffer->data, BUFFER_SIZE, MPI_PACKED, src, rtag, MPI_COMM_WORLD, &rstatus );

            // Check for fragmentation
            int size;
            MPI_Get_count( &rstatus, MPI_PACKED, &size );
            if (size == jsMPI_Integer_Size)
            {
                int zero = 0;
                int numfrags;
                MPI_Unpack( buffer->data, BUFFER_SIZE, &zero, &numfrags, 1, MPI_INT, MPI_COMM_WORLD );

                // Get fragments
                for(int i=0; i<numfrags; i++)
                {
                    MPI_Recv( buffer->data, BUFFER_SIZE, MPI_PACKED, src, rtag, MPI_COMM_WORLD, &rstatus );
                    MPI_Get_count( &rstatus, MPI_PACKED, &size );

                    if (i != numfrags-1)
                    {
                        fragment *newBuffer = new fragment;
                        newBuffer->position = 0;
                        newBuffer->next = buffer;
                        buffer = newBuffer;
                    }
                }
            }
               
            // Unpack object
            last_rcv_tag = rstatus.MPI_TAG;
            last_rcv_source = rstatus.MPI_SOURCE;
            cInfo info;
            Handle<Value> val = jsMPI_Unpack( buffer, &info );
            delete buffer;

            // Check for CArray/CMatrix
            switch ( info.cType )
            {
                case jsMPI_CArray_Int:
                case jsMPI_CArray_Dbl:
                case jsMPI_CArray_Str:
                case jsMPI_CMatrix_Int:
                case jsMPI_CMatrix_Dbl:
                    return ReceiveCType( &info, false, src, rtag );

                default: 
                    return val;
            }


        %>
        
        //****************************** MPI.Probe() ****************************//
        Functions.Probe = <%
            int src = ( args.Length() > 0 ) ? args[0]->Int32Value() : MPI_ANY_SOURCE;
            int tag = ( args.Length() > 1 ) ? args[1]->Int32Value() : MPI_ANY_TAG;

            MPI_Status status;
            int result = MPI_Probe( src, tag, MPI_COMM_WORLD, &status );
            if ( result )
                return ThrowException( String::New( "Error calling MPI.Probe()" ) );

            int size;
            result = MPI_Get_count( &status, MPI_PACKED, &size );
            if ( result )
                return ThrowException( String::New( "Error calling MPI_Get_count" ) );

            Handle<Object> Obj = Object::New( );
            Obj->Set( String::New( "tag" ), Int32::New( status.MPI_TAG ) );
            Obj->Set( String::New( "src" ), Int32::New( status.MPI_SOURCE ) );
            Obj->Set( String::New( "error" ), Int32::New( status.MPI_ERROR ) );
            Obj->Set( String::New( "size" ), Int32::New( size ) );

            return Obj;
        %>
        
        //****************************** MPI.Iprobe() ****************************//
        Functions.Iprobe = <%
            int src = ( args.Length() > 0 ) ? args[0]->Int32Value() : MPI_ANY_SOURCE;
            int tag = ( args.Length() > 1 ) ? args[1]->Int32Value() : MPI_ANY_TAG;
            int flag;

            MPI_Status status;
            int result = MPI_Iprobe( src, tag, MPI_COMM_WORLD, &flag, &status );
            if ( result )
                return ThrowException( String::New( "Error calling MPI.Iprobe()" ) );

            // Return false if given nothing
            if ( !flag )
                return False();

            int size;
            result = MPI_Get_count( &status, MPI_PACKED, &size );
            if ( result )
                return ThrowException( String::New( "Error calling MPI_Get_count" ) );

            Handle<Object> Obj = Object::New( );
            Obj->Set( String::New( "tag" ), Int32::New( status.MPI_TAG ) );
            Obj->Set( String::New( "src" ), Int32::New( status.MPI_SOURCE ) );
            Obj->Set( String::New( "error" ), Int32::New( status.MPI_ERROR ) );
            Obj->Set( String::New( "size" ), Int32::New( size ) );

            return Obj;
        %>

        //****************************** MPI.Barrier() ****************************//
        Functions.Barrier = <%
            int result = MPI_Barrier( MPI_COMM_WORLD );
            if ( result )
                return ThrowException( String::New( "Error using MPI_Barrier" ) );
            else
                return Undefined();
        %>

        //****************************** MPI.Reduce() ****************************//
        // MPI.Reduce( data, op, root );
        // MPI_Reduce( sendbuf, recvbuf, int count, datatype, op, root, comm )
        Functions.Reduce = <%
            if ( args.Length() < 3 )
                return ThrowException( String::New( "Expected data, operation and root" ) );

            // Data storage
            int count = 0;
            void *send, *recv;
            int *send_int, *recv_int;
            double *send_dbl, *recv_dbl;
            char *send_byte, *recv_byte;
            int root = args[2]->Int32Value();
            bool isCType = false;
            bool isString= false;
            MPI_Datatype reduce_type;
            MPI_Op op;
            Handle<Object> RecvObj;

            // Prepare single integer
            if ( args[0]->IsInt32() )
            {
                count = 1;
                send = send_int = new int;
                if ( rank == root )
                    recv = recv_int = new int;
                *send_int = args[0]->Int32Value();
                reduce_type = MPI_INT;
            }

            // Prepare single float
            else if ( args[0]->IsNumber() )
            {
                count = 1;
                send = send_dbl = new double;   
                if ( rank == root )
                    recv = recv_dbl = new double;
                *send_dbl = args[0]->NumberValue();
                reduce_type = MPI_DOUBLE;
            }

            // Prepare single string
            else if ( args[0]->IsString() )
            {
                isString = true;
                Handle<String> Str = args[0]->ToString();
                count = Str->Utf8Length()+1;
                send = send_byte = new char[ count ];
                if ( rank == root )
                    recv = recv_byte = new char[ count ];
                Str->WriteUtf8( send_byte );
                send_byte[ count ] = '\0';
                reduce_type = MPI_BYTE;
            }

            // Prepare simple integer or float arrays
            else if ( args[0]->IsArray() )
            {
                // Determine array type
                Handle<Array> array = Handle<Array>::Cast( args[0] );
                count = array->Length();
                if ( count == 0 )
                    return ThrowException( String::New( "Expected non-empty array" ) );

                if ( array->Get( Int32::New(0) )->IsInt32() )
                {
                    // Array of integers
                    reduce_type = MPI_INT;
                    send = send_int = new int[array->Length()];
                    for (int i=0; i<array->Length(); i++)
                    {
                        Handle<Value> Val = array->Get( Int32::New( i ) );
                        if ( Val != Undefined() )
                            send_int[i] = Val->Int32Value();
                        else
                            send_int[i] = 0;
                    }
                    if ( rank == root )
                        recv = recv_int = new int[array->Length()];
                }
                else if ( array->Get( Int32::New(0) )->IsNumber() )
                {
                    // Array of doubles
                    reduce_type = MPI_DOUBLE;
                    send = send_dbl = new double[array->Length()];
                    for (int i=0; i<array->Length(); i++)
                    {
                        Handle<Value> Val = array->Get( Int32::New( i ) );
                        if ( Val != Undefined() )
                            send_dbl[i] = Val->NumberValue();
                        else
                            send_dbl[i] = 0;
                    }
                    if ( rank == root )
                        recv = recv_dbl = new double[array->Length()];
                }
                else
                    return ThrowException( String::New( "Invalid array type" ) );
            }

            // Support CArray objects
            else if ( args[0]->IsObject() )
            {
                Handle<Object> Obj = args[0]->ToObject();
                if ( Obj->InternalFieldCount() == 2 )
                {
                    int cType = Obj->GetInternalField( 0 )->Int32Value();
                    if ( cType == jsMPI_CArray_Int || cType == jsMPI_CArray_Dbl )
                    {
                        // Integer or double array
                        isCType = true;
                        carray_gen *array = (carray_gen *)Local<External>::Cast( Obj->GetInternalField( 1 ) )->Value();                        
                        count = array->size;
                        send = array->array;
                        if ( cType == jsMPI_CArray_Int )
                        {
                            send_int = (int *)send;
                            reduce_type = MPI_INT;
                            if ( rank == root) 
                            {
                                Handle<Value> ArraySize = Int32::New( array->size );
                                RecvObj = Handle_CArray_Int->NewInstance( 1, &ArraySize );
                                carray_int *rarray = (carray_int *)Local<External>::Cast( RecvObj->GetInternalField( 1 ) )->Value();                        
                                recv = rarray->array;
                            }
                        }
                        else
                        {
                            send_dbl = (double *)send;
                            reduce_type = MPI_DOUBLE;
                            if ( rank == root) 
                            {
                                Handle<Value> ArraySize = Int32::New( array->size );
                                RecvObj = Handle_CArray_Dbl->NewInstance( 1, &ArraySize );
                                carray_double *rarray = (carray_double *)Local<External>::Cast( RecvObj->GetInternalField( 1 ) )->Value();                        
                                recv = rarray->array;
                            }
                        }
                    }
                }
    
                if ( !isCType )
                    return ThrowException( String::New( "Invalid object" ) );
            }
            else
               return ThrowException( String::New( "Invalid datatype" ) );

            // Prepare op
            if ( args[1]->IsInt32() )
            {
                int jsOp = args[1]->Int32Value();
                if ( jsOp < jsMPI_UPPERLIMIT )
                {
                    op = op_table[ jsOp ];
                    if ( !op_dblCompat[ jsOp ] && reduce_type != MPI_INT )
                        return ThrowException( String::New( "Operation requires an integer" ) );
                }
                else
                    return ThrowException( String::New( "Invalid op" ) );
            }
            else if ( args[1]->IsFunction() )
            {
                jsMPI_Op_Handle = Handle<Function>::Cast( args[1] );
                op = jsMPI_Op_Custom;
            }
            else
                return ThrowException( String::New( "Invalid op" ) );

            // Reduce
            recv_int = (int *)recv;
            recv_int[0] = 0;
            int result = MPI_Reduce( send, recv, count, reduce_type, op, root, MPI_COMM_WORLD );
            if ( result )
                return ThrowException( String::New( "Error calling MPI.Reduce" ) );
            else if ( isCType )
            {
                if ( rank == root )
                    return RecvObj;
                else
                    return False();
            }
            else if ( isString )
                if ( rank == root )
                {
                    char *recv_byte = (char *)recv;
                    Handle<Value> ValObj = String::New( recv_byte );                    
                    delete recv_byte;
                    return ValObj;
                }

            // Prepare result
            if ( rank == root )
            {
                if ( count == 1 )
                {
                    // Dump to variable
                    Handle<Value> Val;
                    if ( reduce_type == MPI_INT )
                    {
                        Val = Int32::New( *recv_int );
                        delete send_int;
                        if ( rank == root )
                            delete recv_int;
                    }
                    else
                    {
                        Val = Number::New( *recv_dbl );
                        delete send_dbl;
                        if ( rank == root )
                            delete recv_dbl;
                    }

                    return Val;
                }
                else
                {
                    // Dump to array
                    Handle<Array> ReturnVal = Array::New();
                    if ( reduce_type == MPI_INT )
                    {
                        for ( int i=0; i<count; i++ )
                            ReturnVal->Set( Int32::New( i ), Int32::New( recv_int[i] ) );

                        delete send_int;
                        delete recv_int;
                    }
                    else
                    {
                        for ( int i=0; i<count; i++ )
                            ReturnVal->Set( Number::New( i ), Number::New( recv_dbl[i] ) );

                        delete send_dbl;
                        delete recv_dbl;
                    }

                    return ReturnVal;
                }
            }
            else
                return False();
    
        %>
        
        //****************************** MPI.Wtick() ****************************//
        Functions.Wtick = <%
            return Number::New( MPI_Wtick() );
        %>        
        
        //****************************** MPI.Wtime() ****************************//
        Functions.Wtime = <%
            return Number::New( MPI_Wtime() );
        %>    
        
        //****************************** MPI.Exit() ****************************//
        Exit = <%

            MPI_Op_free( &jsMPI_Op_Custom );
    
            // Clear constructor pointers
            Handle_CArray_Int.Dispose();
            Handle_CArray_Dbl.Dispose();
            Handle_CArray_Str.Dispose();
            Handle_CMatrix_Int.Dispose();
            Handle_CMatrix_Dbl.Dispose();
            Handle_CArray_Int.Clear();
            Handle_CArray_Dbl.Clear();
            Handle_CArray_Str.Clear();
            Handle_CMatrix_Int.Clear();
            Handle_CMatrix_Dbl.Clear();

            // Exit MPI
            MPI_Finalize();
        %>
        
        Export();
}
